/*
 * jxSwing Project (2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxSwing.component.table;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;

import org.jxSwing.Cleanable;
import org.jxSwing.component.table.annotation.Column;
import org.jxUtils.TypeUtil;
import org.jxUtils.check.ParameterCheck;
import org.jxUtils.collection.ListSet;
import org.jxUtils.exception.InvalidValueException;

/**
 * @author Denilson Edinaldo Pinto
 * @porject jxSwing
 * @package org.jxSwing.component.table
 * @date 27/01/2014 - 22:42:22
 * @param <Bean>
 */
public class BeanTableModel<Bean> extends AbstractTableModel implements Cleanable {
	
	private static final long serialVersionUID = -8446499552699233354L;
	
	private Class<?> beanClass;
	
	private ListSet<BeanTableColumn> beanTableColumns = new ListSet<>();
	
	private boolean locked = false;
	
	private final List<Bean> rows = new ArrayList<>();
	
	private final List<Boolean> rowsEditable = new ArrayList<>();
	
	private boolean acceptDuplicateRows = true;
	
	/**
	 * @param beanTableColumn
	 * @param beans
	 */
	BeanTableModel(final BeanTableColumn beanTableColumn, final Collection<Bean> beans) {
	
		beanClass = beans.iterator().next().getClass();
		
		makeColumns(beanTableColumn, getBeanClass());
		
		arrangeColumn();
		
		addRows(new ArrayList<>(beans));
	}
	
	/**
	 * @param bean
	 */
	public BeanTableModel(final Bean bean) {
	
		this(bean.getClass());
		
		addRow(bean);
	}
	
	/**
	 * @param beanClass
	 */
	public BeanTableModel(final Class<?> beanClass) {
	
		this(beanClass, new ArrayList<Bean>());
	}
	
	/**
	 * @param beanClass
	 * @param beans
	 */
	public BeanTableModel(final Class<?> beanClass, final Collection<Bean> beans) {
	
		this.beanClass = beanClass;
		
		makeColumns(getBeanClass());
		
		arrangeColumn();
		
		addRows(new ArrayList<>(beans));
	}
	
	/**
	 * @param beans
	 */
	public BeanTableModel(final Collection<Bean> beans) {
	
		this(beans.iterator().next().getClass(), beans);
	}
	
	/**
	 * @param rowIndex
	 * @param columnIndex
	 * @return value
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	private Object getColumnValue(final int rowIndex, final int columnIndex) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
	
		final Bean bean = getRows().get(rowIndex);
		
		// 
		if(bean != null) {
			
			// Se for um Tipo Primitivo
			if(TypeUtil.isPrimitive(bean.getClass()) || TypeUtil.isImageIcon(bean)) {
				
				return bean;
				
			}else {
				
				final Method method = getColumn(columnIndex).getBeanMethodGet();
				
				return method != null ? method.invoke(bean) : null;
			}
		}
		
		return null;
	}
	
	/**
	 * @param from
	 * @param to
	 */
	private void justifyRows(final int from, final int to) {
	
		//
		for(int index = from; index < to; index++) {
			
			//
			if(getRows().get(index) == null) {
				
				getRows().add(index, null);
			}
		}
	}
	
	/**
	 * @param columnName
	 * @return Column Alias
	 */
	private String makeColumnAlias(final String columnName) {
	
		final StringBuilder columnAlias = new StringBuilder();
		
		columnAlias.append(Character.toUpperCase(columnName.charAt(0)));
		
		//
		for(int index = 1; index < columnName.length(); index++) {
			
			//
			if(Character.isUpperCase(columnName.charAt(index))) {
				
				columnAlias.append(" ");
			}
			
			columnAlias.append(columnName.charAt(index));
		}
		
		return columnAlias.toString();
	}
	
	/**
	 * @param beanTableColumn
	 * @param beanClass
	 */
	private void makeColumns(final BeanTableColumn beanTableColumn, final Class<?> beanClass) {
	
		final BeanTableColumn newColumn = new BeanTableColumn(beanTableColumn.getName(), beanTableColumn.getAlias(), beanClass);
		
		getColumns().add(newColumn);
	}
	
	/**
	 * @param beanClass
	 */
	private void makeColumns(final Class<?> beanClass) {
	
		//
		if(TypeUtil.isBean(beanClass.getSuperclass())) {
			
			makeColumns(beanClass.getSuperclass());
		}
		
		try {
			//
			final PropertyDescriptor[] descriptors = Introspector.getBeanInfo(beanClass).getPropertyDescriptors();
			
			//
			for(final Field field : beanClass.getDeclaredFields()) {
				
				//
				for(final PropertyDescriptor property : descriptors) {
					
					//
					if(property.getName().equalsIgnoreCase(field.getName())) {
						
						final Method methodGet = property.getReadMethod();
						final Method methodSet = property.getWriteMethod();
						
						//
						if(methodGet != null && methodSet != null) {
							
							final String columnName = property.getName().toUpperCase();
							
							String columnAlias = makeColumnAlias(property.getName());
							Column annotationColumn = null;
							
							// Se o Atributo possui Annotations
							for(final Annotation annotation : field.getDeclaredAnnotations()) {
								
								// Se a Annotation for do tipo ColumnConfig
								if(annotation instanceof Column) {
									
									annotationColumn = (Column)annotation;
									
									columnAlias = annotationColumn.name().trim();
									
									// Se possui um Apelido, então o assume, caso contrário mantém o nome do Atributo
									columnAlias = columnAlias.isEmpty() ? columnName : columnAlias;
									
									break;
								}
							}
							
							//
							final Class<?> type = TypeUtil.getPrimitiveWrapper(property.getPropertyType());
							
							final BeanTableColumn beanTableColumn = new BeanTableColumn(annotationColumn, columnName, columnAlias, TypeUtil.isMap(type) ? List.class : type, methodGet);
							
							beanTableColumn.setBeanMethodSet(methodSet);
							
							getColumns().add(beanTableColumn);
						}
						
						break;
					}
				}
			}
			
		}catch(SecurityException | IntrospectionException exception) {
			
			throw new RuntimeException(exception.getMessage(), exception);
		}
	}
	
	/**
	 * @param column
	 */
	private void setColumns(final ListSet<BeanTableColumn> beanTableColumns) {
	
		this.beanTableColumns = beanTableColumns;
	}
	
	/**
	 * @param locked
	 */
	final void setLocked(final boolean locked) {
	
		//
		for(final BeanTableColumn beanTableColumn : getColumns()) {
			
			//
			if( !TypeUtil.isPrimitive(beanTableColumn.getType()) && !TypeUtil.isImageType(beanTableColumn.getType()) && !beanTableColumn.getType().isEnum()) {
				
				beanTableColumn.setEditable(true);
				
			}else {
				
				beanTableColumn.setEditable( !locked);
			}
		}
		
		//
		for(int rowIndex = 0; rowIndex < rowsEditable.size(); rowIndex++) {
			
			rowsEditable.set(rowIndex, !locked);
		}
		
		this.locked = locked;
	}
	
	/**
	 * Inserts a new Bean in the last row of the table
	 * 
	 * @return last row index added or -1
	 */
	@SuppressWarnings("unchecked")
	public final synchronized int addRow() {
	
		try {
			//
			return addRow((Bean)getBeanClass().newInstance());
			
		}catch(InstantiationException | IllegalAccessException exception) {
			
			exception.printStackTrace();
		}
		
		return -1;
	}
	
	/**
	 * Inserts bean in the last row of the table
	 * 
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRow(final Bean bean) {
	
		// Se o Item ainda não existe, então adiciona
		if(isAcceptDuplicateRows() || !getRows().contains(bean)) {
			
			getRows().add(bean);
			
			rowsEditable.add( !isLocked());
		}
		
		final int rowIndex = getLastRowIndex();
		
		justifyRows(0, rowIndex);
		
		/*
		 * notifica que as linhas na faixa especificada foram adicionadas,
		 * fazendo com que a JTable redesenhe apenas as linhas que foram afetadas
		 */
		fireTableRowsInserted(rowIndex, rowIndex);
		
		return rowIndex;
	}
	
	/**
	 * Inserts bean in rowIndex of the table
	 * 
	 * @param rowIndex
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRow(final int rowIndex, final Bean bean) {
	
		// Se o Item ainda não existe, então adiciona
		if(isAcceptDuplicateRows() || !getRows().contains(bean)) {
			
			//
			if(getRowCount() == 0) {
				
				getRows().add(bean);
				
				rowsEditable.add( !isLocked());
				
			}else {
				
				getRows().add(rowIndex, bean);
				
				rowsEditable.add(rowIndex, !isLocked());
			}
		}
		
		/*
		 * notifica que as linhas na faixa especificada foram adicionadas,
		 * fazendo com que a JTable redesenhe apenas as linhas que foram afetadas
		 */
		fireTableRowsInserted(rowIndex, rowIndex);
		
		return rowIndex;
	}
	
	/**
	 * Inserts beans in rowIndex of the table
	 * 
	 * @param rowIndex
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRow(final int rowIndex, final Collection<Bean> beans) {
	
		ParameterCheck.isNull(beans, "beans");
		
		int index = rowIndex;
		
		//
		if(getRowCount() == 0) {
			
			//
			for(final Bean bean : beans) {
				
				// Se o Item ainda não existe, então adiciona
				if(isAcceptDuplicateRows() || !getRows().contains(bean)) {
					
					getRows().add(bean);
					
					rowsEditable.add( !isLocked());
				}
			}
			
		}else {
			
			//
			for(final Bean bean : beans) {
				
				// Se o Item ainda não existe, então adiciona
				if(isAcceptDuplicateRows() || !getRows().contains(bean)) {
					
					getRows().add(index++, bean);
					
					rowsEditable.add(index++, !isLocked());
				}
			}
		}
		
		index = index > 0 ? index-- : 0;
		
		justifyRows(0, index);
		
		/*
		 * notifica que as linhas na faixa especificada foram adicionadas,
		 * fazendo com que a JTable redesenhe apenas as linhas que foram afetadas
		 */
		fireTableRowsInserted(rowIndex, index);
		
		return index;
	}
	
	/**
	 * Inserts a new Bean after rowIndex of the table
	 * 
	 * @return last row index added or -1
	 */
	@SuppressWarnings("unchecked")
	public final synchronized int addRowAfter(final int rowIndex) {
	
		try {
			//
			return addRowAfter(rowIndex, (Bean)getBeanClass().newInstance());
			
		}catch(InstantiationException | IllegalAccessException exception) {
			
			exception.printStackTrace();
		}
		
		return -1;
	}
	
	/**
	 * Inserts bean after rowIndex of the table
	 * 
	 * @param rowIndex
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRowAfter(final int rowIndex, final Bean bean) {
	
		int index = rowIndex + 1;
		
		index = index > getLastRowIndex() ? getLastRowIndex() : index;
		
		index = index < 0 ? 0 : index;
		
		index = getRowCount() == 0 ? 0 : index;
		
		return addRow(index, bean);
	}
	
	/**
	 * Inserts a new Bean before rowIndex of the table
	 * 
	 * @return last row index added or -1
	 */
	@SuppressWarnings("unchecked")
	public final synchronized int addRowBefore(final int rowIndex) {
	
		try {
			//
			return addRowBefore(rowIndex, (Bean)getBeanClass().newInstance());
			
		}catch(InstantiationException | IllegalAccessException exception) {
			
			exception.printStackTrace();
		}
		
		return -1;
	}
	
	/**
	 * Inserts bean before rowIndex of the table
	 * 
	 * @param rowIndex
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRowBefore(final int rowIndex, final Bean bean) {
	
		int index = rowIndex - 1;
		
		index = index < 0 ? 0 : index;
		
		index = index > getLastRowIndex() ? getLastRowIndex() : index;
		
		index = getRowCount() == 0 ? 0 : index;
		
		return addRow(index, bean);
	}
	
	/**
	 * @param beans
	 * @return last row index added or -1
	 */
	public final synchronized int addRows(final Bean[] beans) {
	
		ParameterCheck.isNull(beans, "beans");
		
		return addRows(Arrays.asList(beans));
	}
	
	/**
	 * @param beans
	 * @return last row index added or -1
	 */
	public final synchronized int addRows(final Collection<Bean> beans) {
	
		ParameterCheck.isNull(beans, "beans");
		
		int lastRowIndexAdded = -1;
		
		//
		for(final Bean bean : beans) {
			
			lastRowIndexAdded = addRow(bean);
		}
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts beans after rowIndex of the table
	 * 
	 * @param beans
	 * @return last row index added or -1
	 */
	public final synchronized int addRowsAfter(final int rowIndex, final Bean[] beans) {
	
		return addRowsAfter(rowIndex, Arrays.asList(beans));
	}
	
	/**
	 * Inserts beans after rowIndex of the table
	 * 
	 * @param beans
	 * @return last row index added
	 */
	public final synchronized int addRowsAfter(final int rowIndex, final Collection<Bean> beans) {
	
		return addRow(rowIndex + 1, beans);
	}
	
	/**
	 * Inserts beans before rowIndex of the table
	 * 
	 * @param beans
	 * @return last row index added
	 */
	public final synchronized int addRowsBefore(final int rowIndex, final Bean[] beans) {
	
		return addRowsBefore(rowIndex, Arrays.asList(beans));
	}
	
	/**
	 * Inserts beans before rowIndex of the table
	 * 
	 * @param beans
	 * @return last row index added
	 */
	public final synchronized int addRowsBefore(final int rowIndex, final Collection<Bean> beans) {
	
		return addRow(rowIndex - 1, beans);
	}
	
	/**
	 *
	 */
	public final void arrangeColumn() {
	
		//
		final List<BeanTableColumn> arrangeColumns = new ArrayList<>(getColumns().size());
		
		//
		final List<Integer> indexAlreadyOrganized = new ArrayList<>();
		
		//
		for(int index = 0; index < getColumns().size(); index++) {
			
			arrangeColumns.add(null);
		}
		
		//
		for(final BeanTableColumn beanTableColumn : getColumns()) {
			
			//
			if(beanTableColumn.getIndex() >= 0) {
				
				//
				if(indexAlreadyOrganized.contains(beanTableColumn.getIndex())) {
					
					throw new InvalidValueException();
				}
				
				//
				arrangeColumns.set(beanTableColumn.getIndex(), beanTableColumn);
				
				//
				indexAlreadyOrganized.add(beanTableColumn.getIndex());
			}
		}
		
		//
		for(final BeanTableColumn beanTableColumn : getColumns()) {
			
			//
			if(beanTableColumn.getIndex() < 0) {
				
				//
				for(int index = 0; index < arrangeColumns.size(); index++) {
					
					//
					if(arrangeColumns.get(index) == null) {
						
						arrangeColumns.set(index, beanTableColumn);
						
						break;
					}
				}
			}
		}
		
		//
		setColumns(new ListSet<>(arrangeColumns));
	}
	
	@Override
	public final void clear() {
	
		getRows().clear();
		rowsEditable.clear();
		
		fireTableDataChanged();
	}
	
	/**
	 * @return the Bean
	 */
	public final Bean getBean(final int rowIndex) {
	
		return getRow(rowIndex);
	}
	
	/**
	 * @return the beanClass
	 */
	public final Class<?> getBeanClass() {
	
		return beanClass;
	}
	
	/**
	 * @param columnIndex
	 * @return column or null
	 */
	public final BeanTableColumn getColumn(final int columnIndex) {
	
		final Iterator<BeanTableColumn> iterator = getColumns().iterator();
		
		BeanTableColumn beanTableColumn = null;
		
		int count = 0;
		
		//
		while(iterator.hasNext()) {
			
			beanTableColumn = iterator.next();
			
			//
			if(count == columnIndex) {
				
				return beanTableColumn;
			}
			
			count++;
		}
		
		return null;
	}
	
	/**
	 * @param columnIndex
	 * @return column
	 */
	public final String getColumnAlias(final int columnIndex) {
	
		return getColumn(columnIndex).getAlias();
	}
	
	/**
	 * @param columnName
	 * @return column
	 */
	public final String getColumnAlias(final String columnName) {
	
		return getColumnByName(columnName).getAlias();
	}
	
	/**
	 * @param columnAlias
	 * @return column or null
	 */
	public final BeanTableColumn getColumnByAlias(final String columnAlias) {
	
		final Iterator<BeanTableColumn> iterator = getColumns().iterator();
		
		BeanTableColumn beanTableColumn = null;
		
		//
		while(iterator.hasNext()) {
			
			beanTableColumn = iterator.next();
			
			//
			if(beanTableColumn.getAlias().equals(columnAlias)) {
				
				return beanTableColumn;
			}
		}
		
		return null;
	}
	
	/**
	 * @param columnName
	 * @return column or null
	 */
	public final BeanTableColumn getColumnByName(final String columnName) {
	
		final Iterator<BeanTableColumn> iterator = getColumns().iterator();
		
		BeanTableColumn beanTableColumn = null;
		
		//
		while(iterator.hasNext()) {
			
			beanTableColumn = iterator.next();
			
			//
			if(beanTableColumn.getName().equalsIgnoreCase(columnName)) {
				
				return beanTableColumn;
			}
		}
		
		return null;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.table.AbstractTableModel#getColumnClass(int)
	 */
	@Override
	public final Class<?> getColumnClass(final int columnIndex) {
	
		final BeanTableColumn beanTableColumn = getColumn(columnIndex);
		
		return beanTableColumn != null ? beanTableColumn.getType() : null;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.table.TableModel#getColumnCount()
	 */
	@Override
	public final int getColumnCount() {
	
		return getColumns().size();
	}
	
	/**
	 * @param columnName
	 * @return index
	 */
	public final int getColumnIndex(final String columnName) {
	
		final Iterator<BeanTableColumn> iterator = getColumns().iterator();
		
		BeanTableColumn beanTableColumn = null;
		
		int index = 0;
		
		//
		while(iterator.hasNext()) {
			
			beanTableColumn = iterator.next();
			
			//
			if(beanTableColumn.getName().equalsIgnoreCase(columnName)) {
				
				return index;
			}
			
			index++;
		}
		
		return -1;
	}
	
	/**
	 * @param column
	 *        the column being queried
	 * @return a string containing the name of <code>column</code>
	 */
	@Override
	public final String getColumnName(final int columnIndex) {
	
		return getColumn(columnIndex).getAlias();
	}
	
	/**
	 * @param columnAlias
	 * @return a string containing the name of <code>column</code>
	 */
	public final String getColumnName(final String columnAlias) {
	
		return getColumnByAlias(columnAlias).getName();
	}
	
	/**
	 * @return the columns
	 */
	public final ListSet<BeanTableColumn> getColumns() {
	
		return beanTableColumns;
	}
	
	/**
	 * @return the columns classes
	 */
	public final List<Class<?>> getColumnsClasses() {
	
		final List<Class<?>> columnsClasses = new ArrayList<>();
		
		for(final BeanTableColumn column : getColumns()) {
			
			columnsClasses.add(column.getType());
		}
		
		return columnsClasses;
	}
	
	/**
	 * @param columnIndex
	 * @return column values
	 */
	public final List<Object> getColumnValues(final int columnIndex) {
	
		final List<Object> values = new ArrayList<>();
		
		//
		for(int rowIndex = 0; rowIndex < getRowCount(); rowIndex++) {
			
			values.add(getValueAt(rowIndex, columnIndex));
		}
		
		return values;
	}
	
	/**
	 * @param columnAlias
	 * @return column values
	 */
	public final List<Object> getColumnValuesByAlias(final String columnAlias) {
	
		return getColumnValuesByName(getColumnName(columnAlias));
	}
	
	/**
	 * @param columnName
	 * @return column values
	 */
	public final List<Object> getColumnValuesByName(final String columnName) {
	
		return getColumnValues(getColumnIndex(columnName));
	}
	
	/**
	 * @return the First Bean
	 */
	public final Bean getFirstBean() {
	
		return getFirstRow();
	}
	
	/**
	 * @return the First Bean
	 */
	public final Bean getFirstRow() {
	
		return getRows().get(0);
	}
	
	/**
	 * @return the Last Bean
	 */
	public final Bean getLastBean() {
	
		return getLastRow();
	}
	
	/**
	 * @return the Last Bean
	 */
	public final Bean getLastRow() {
	
		return getRows().get(getLastRowIndex());
	}
	
	/**
	 * @return last row index
	 */
	public final int getLastRowIndex() {
	
		return getRowCount() <= 0 ? 0 : getRowCount() - 1;
	}
	
	/**
	 * @return the Bean
	 */
	public final Bean getRow(final int rowIndex) {
	
		return getRows().get(rowIndex);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.table.TableModel#getRowCount()
	 */
	@Override
	public final int getRowCount() {
	
		return getRows().size();
	}
	
	/**
	 * @return the rows
	 */
	public final List<Bean> getRows() {
	
		return rows;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.table.TableModel#getValueAt(int, int)
	 */
	@Override
	public final Object getValueAt(final int rowIndex, final int columnIndex) {
	
		try {
			//
			return getColumnValue(rowIndex, columnIndex);
			
		}catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
			
			throw new RuntimeException(exception);
		}
	}
	
	/**
	 * @return the acceptDuplicateRows
	 */
	public final boolean isAcceptDuplicateRows() {
	
		return acceptDuplicateRows;
	}
	
	/**
	 *
	 */
	@Override
	public final boolean isCellEditable(final int rowIndex, final int columnIndex) {
	
		//
		if(isRowEditable(rowIndex)) {
			
			return true;
		}
		
		return isColumnEditable(columnIndex);
	}
	
	/**
	 * @param columnIndex
	 * @return column is editable
	 */
	public final boolean isColumnEditable(final int columnIndex) {
	
		return getColumn(columnIndex).isEditable();
	}
	
	/**
	 * @return the locked
	 */
	public final boolean isLocked() {
	
		return locked;
	}
	
	/**
	 * @param rowIndex
	 * @return row is editable
	 */
	public final boolean isRowEditable(final int rowIndex) {
	
		return rowsEditable.get(rowIndex);
	}
	
	/**
	 *
	 */
	public final void locked() {
	
		setLocked(true);
	}
	
	/**
	 * @param tableModelEvent
	 */
	public final void newDataAvailable(final TableModelEvent tableModelEvent) {
	
		fireTableChanged(tableModelEvent);
	}
	
	/**
	 * @param tableModelEvent
	 */
	public final void newRowsAdded(final TableModelEvent tableModelEvent) {
	
		justifyRows(tableModelEvent.getFirstRow(), tableModelEvent.getLastRow() + 1);
		
		fireTableChanged(tableModelEvent);
	}
	
	/**
	 * @param bean
	 */
	public final void removeRow(final Bean bean) {
	
		removeRow(getRows().indexOf(bean));
	}
	
	/**
	 * @param rowIndex
	 */
	public final void removeRow(final int rowIndex) {
	
		getRows().remove(rowIndex);
		rowsEditable.remove(rowIndex);
		
		fireTableRowsDeleted(rowIndex, rowIndex);
	}
	
	/**
	 * @param tableModelEvent
	 */
	public final void rowsRemoved(final TableModelEvent tableModelEvent) {
	
		fireTableChanged(tableModelEvent);
	}
	
	/**
	 * @param acceptDuplicateRows
	 *        the acceptDuplicateRows to set
	 */
	public final void setAcceptDuplicateRows(final boolean acceptDuplicateRows) {
	
		this.acceptDuplicateRows = acceptDuplicateRows;
	}
	
	/**
	 * @param columnIndex
	 * @param columnAlias
	 */
	public final void setColumnAlias(final int columnIndex, final String columnAlias) {
	
		getColumn(columnIndex).setAlias(columnAlias);
	}
	
	/**
	 * @param columnName
	 * @param columnAlias
	 */
	public final void setColumnAlias(final String columnName, final String columnAlias) {
	
		getColumnByName(columnName).setAlias(columnAlias);
	}
	
	/**
	 * @param columnIndex
	 * @param editable
	 */
	public final void setColumnEditable(final int columnIndex, final boolean editable) {
	
		getColumn(columnIndex).setEditable(editable);
	}
	
	/**
	 * @param rowIndex
	 * @param editable
	 */
	public final void setRowEditable(final int rowIndex, final boolean editable) {
	
		rowsEditable.set(rowIndex, editable);
	}
	
	/**
	 * @param rows
	 *        the rows to set
	 */
	public final void setRows(final Bean[] beans) {
	
		ParameterCheck.isNull(beans, "beans");
		
		setRows(Arrays.asList(beans));
	}
	
	/**
	 * @param rows
	 *        the rows to set
	 */
	public final void setRows(final Collection<Bean> beans) {
	
		ParameterCheck.isNull(beans, "beans");
		
		//
		if( !getRows().equals(beans)) {
			
			clear();
			
			addRows(beans);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.table.AbstractTableModel#setValueAt(java.lang.Object, int, int)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public final void setValueAt(final Object value, final int rowIndex, final int columnIndex) {
	
		try {
			//
			final BeanTableColumn beanTableColumn = getColumn(columnIndex);
			
			final Bean bean = getRows().get(rowIndex);
			
			// Se for um tipo Primitivo
			if(TypeUtil.isPrimitive(bean.getClass()) || TypeUtil.isImageIcon(bean)) {
				
				getRows().set(rowIndex, (Bean)value);
				
			}else {
				
				//
				if(beanTableColumn.getBeanMethodSet() != null) {
					
					final Class<?> atributteType = beanTableColumn.getBeanMethodGet().getReturnType();
					
					//
					if(atributteType.equals(Time.class)) {
						
						final Date date = (Date)value;
						
						beanTableColumn.getBeanMethodSet().invoke(bean, new Time(date.getTime()));
						
					}else {
						
						Object valueSet = value;
						
						//
						if(value instanceof Number) {
							
							//
							if(Byte.class.equals(atributteType) || byte.class.equals(atributteType)) {
								
								//
								if(value != null && !(valueSet instanceof Byte)) {
									
									valueSet = ((Number)value).byteValue();
								}
								
							}else if(Short.class.equals(atributteType) || short.class.equals(atributteType)) {
								
								//
								if(value != null && !(valueSet instanceof Short)) {
									
									valueSet = ((Number)value).shortValue();
								}
								
							}else if(Integer.class.equals(atributteType) || int.class.equals(atributteType)) {
								
								//
								if(value != null && !(valueSet instanceof Integer)) {
									
									valueSet = ((Number)value).intValue();
								}
								
							}else if(Long.class.equals(atributteType) || long.class.equals(atributteType)) {
								
								//
								if(value != null && !(valueSet instanceof Long)) {
									
									valueSet = ((Number)value).longValue();
								}
								
							}else if(BigInteger.class.equals(atributteType)) {
								
								//
								if(value != null && !(valueSet instanceof BigInteger)) {
									
									valueSet = new BigInteger(String.valueOf(((Number)value).intValue()));
								}
								
							}else if(Float.class.equals(atributteType) || float.class.equals(atributteType)) {
								
								//
								if(value != null && !(valueSet instanceof Float)) {
									
									valueSet = ((Number)value).floatValue();
								}
								
							}else if(Double.class.equals(atributteType) || double.class.equals(atributteType)) {
								
								//
								if(value != null && !(valueSet instanceof Double)) {
									
									valueSet = ((Number)value).doubleValue();
								}
								
							}else if(BigDecimal.class.equals(atributteType)) {
								
								//
								if(value != null && !(valueSet instanceof BigDecimal)) {
									
									valueSet = new BigDecimal(value.toString());
								}
							}
						}
						
						//
						if(valueSet != null && !atributteType.equals(valueSet.getClass())) {
							
							//
							if(valueSet instanceof Collection<?>) {
								
								final Collection<?> collection = (Collection<?>)valueSet;
								
								//
								if( !collection.isEmpty()) {
									
									final Object valueInCollection = collection.iterator().next();
									
									//
									if(atributteType.equals(valueInCollection.getClass())) {
										
										valueSet = valueInCollection;
									}
								}
							}
						}
						
						//
						if(valueSet == null && boolean.class.equals(atributteType)) {
							
							valueSet = false;
						}
						
						//
						beanTableColumn.getBeanMethodSet().invoke(bean, valueSet);
					}
				}
			}
			
		}catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
			
			throw new RuntimeException(exception);
		}
		
		/*
		 * notifica que o conteúdo da célula especificada foi atualizado,
		 * fazendo com que a JTable redesenhe apenas a célula em questão.
		 */
		fireTableCellUpdated(rowIndex, columnIndex);
	}
	
	/**
	 *
	 */
	public final void unLocked() {
	
		setLocked(false);
	}
}
